Skip to content

fix: pagination for null-datetime items and empty fields object#1135

Merged
matthewhanson merged 5 commits into
mainfrom
fix/pagination-null-datetime
Jul 1, 2026
Merged

fix: pagination for null-datetime items and empty fields object#1135
matthewhanson merged 5 commits into
mainfrom
fix/pagination-null-datetime

Conversation

@matthewhanson

Copy link
Copy Markdown
Member

Summary

Fixes the pagination bugs tracked for 5.0.1.

1. Null-datetime pagination (#608, #1082)

Items with datetime: null (only start_datetime/end_datetime set) broke pagination: OpenSearch sorts missing dates to the Long.MIN sentinel, which (a) loses precision when parsed in JS and (b) the lossy sort.join(',') / split(',') next token corrupted further — the follow-up page 400'd.

  • next token is now base64url-encoded JSON of the sort values (round-trips numbers/nulls losslessly). buildSearchAfter still accepts the legacy comma format.
  • The default sort uses missing: 0 for properties.datetime, giving missing dates a concrete, reusable search_after value instead of the sentinel.

2. fields={} strips the page-2 body

An empty fields object was serialized into the GET next link as fields={}, then re-parsed on the follow-up request as a field literally named {} — returning items with an empty _source. Empty objects/arrays are now dropped from next-link params.

#823 — already fixed

Confirmed the "next link missing when sortby field excluded" case is already resolved by the 5.0.0 sort rework (#1046); added a regression test so it stays fixed.

Tests

New tests/system/test-api-pagination.ts (null-datetime traversal + sortby-excluded). Updated two test-api-search-get assertions for the new token format. Verified: typecheck, lint, unit (69), and the pagination-affected system suite all green.

Closes #608
Closes #1082
Closes #823

🤖 Generated with Claude Code

Two pagination defects affecting GET /search and the items endpoints:

1. Items without a `datetime` (only start/end_datetime) broke pagination.
   OpenSearch sorts missing dates to a Long sentinel that loses precision
   in JS and 400s when reused as `search_after`. Fix: the default sort now
   uses `missing: 0` for `properties.datetime`, and the `next` token is
   base64url-encoded JSON of the sort values (lossless for numbers/nulls)
   instead of a lossy comma-join. buildSearchAfter still accepts the legacy
   comma format. (#608, #1082)

2. An empty `fields` object serialized into the GET `next` link as
   `fields={}`, which on the follow-up request parsed as a field named
   "{}" and stripped the response _source. Fix: drop empty objects/arrays
   from next-link params.

Adds test-api-pagination.ts covering both, plus a regression for #823
(next link present when the sortby field is excluded — already fixed by
the #1046 sort rework). Updates two next-token assertions for the new
token format.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes pagination edge cases in the STAC API by making next tokens lossless (base64url JSON of OpenSearch sort values), stabilizing the default datetime sort for missing datetimes, and preventing empty request params (notably fields: {}) from corrupting subsequent-page requests.

Changes:

  • Encode next as base64url JSON (with legacy comma-token fallback) and adjust default sort to use missing: 0 for properties.datetime.
  • Drop empty objects/arrays from pagination link params to avoid fields={} poisoning page-2 requests.
  • Add/adjust system tests to cover null-datetime pagination and sortby-excluded pagination behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/lib/database.ts Updates default sort and implements base64url JSON next token parsing/encoding for search_after.
src/lib/api.ts Filters empty values out of generated pagination link params to avoid fields={} in next links.
src/lib/types.ts Broadens OpenSearch request typing to allow numeric/null search_after values and optional missing in sort rules.
tests/system/test-api-search-get.ts Updates assertions to decode/verify the new base64url JSON next token format.
tests/system/test-api-pagination.ts Adds system regressions for null-datetime pagination and sortby-excluded pagination.
CHANGELOG.md Documents the pagination fixes.
Comments suppressed due to low confidence (1)

src/lib/api.ts:364

  • For GET pagination links, fields has already been parsed into an object, and dictToURI will JSON.stringify it. On the follow-up GET request, extractFields treats fields as the GET comma syntax (not JSON), so any non-empty fields filter will be re-parsed into garbage and can strip _source on page 2. Convert the fields object back into the GET comma-separated format when building GET next links (but keep the object form for POST link bodies).
      const nextParams = pickBy(
        assign(parameters, { bbox, intersects, limit, next: lastItemSort, collections, filter }),
        isPresent
      )
      if (httpMethod === 'GET') {
        const nextQueryParameters = dictToURI(nextParams)
        link.href = `${endpoint}?${nextQueryParameters}`

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/system/test-api-pagination.ts
Per review: `followNext` always issued a GET, but POST /search returns
POST-style next links (token in the body, not the href), so the
sortby-excluded regression test wasn't actually validating page 2.
Follow the link's method, and assert page 2 returns a different item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@matthewhanson matthewhanson mentioned this pull request Jun 20, 2026
7 tasks
@jkeifer

jkeifer commented Jul 1, 2026

Copy link
Copy Markdown
Member

I worked with claude to help me understand what this code is for and how it works. It identified a discrepancy between the pairing of buildSort and buidSearchAfter, and wrote a test to confirm a bug was present. I had it create a fix for that bug on the buildSort side; those changes are available for review in #1161.

A custom `sortby` replaced the default sort's unique `id`/`collection`
tiebreakers. Items sharing the user's sort value — worst when the sort
field is absent on some items and they all collapse to the same
missing-value key — could not be disambiguated by `search_after`, so the
follow-up page skipped them. buildSort now appends the `id` tiebreaker
the default sort already guarantees, unless the caller already sorts by id.

Adds a system test paginating a custom sort where half the items lack the
sort field; it fails without the tiebreaker. Also corrects comments (and
the CHANGELOG) that credited base64 token encoding with fixing the Long
sentinel — the sentinel is lossy before encoding; `missing: 0` on the
default sort and the tiebreaker on custom sorts are what actually fix it.

Refs #608, #1082
@matthewhanson matthewhanson added this pull request to the merge queue Jul 1, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 1, 2026
@matthewhanson matthewhanson enabled auto-merge July 1, 2026 19:27
@matthewhanson matthewhanson added this pull request to the merge queue Jul 1, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 1, 2026
@matthewhanson matthewhanson enabled auto-merge July 1, 2026 19:45
@matthewhanson matthewhanson added this pull request to the merge queue Jul 1, 2026
Merged via the queue into main with commit 760b7aa Jul 1, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants